package auditoryUI;

import java.util.Collections;
import java.util.HashMap;
import java.util.Vector;

import auditoryTree.TreeModel;
import auditoryUIbk.SaxSunburstLoader;
import auditoryUIbk.SoundManager;
import auditoryUIbk.SoundSet;

/**
 * This class takes the coordinates on the touch pad and find the corresponding
 * menu item that the point is at.
 * 
 * @author Stephen
 * 
 */
public class MenuHandler {

	public final boolean DEBUG = true;

	// 1 ** JASS sound configuration options
	// The audio API used: javasound, directx, rtaudio or asio.
	// Test between javasound or directx. directx requires rtaudio.dll
	static String soundAPI = "javasound";

	// JASS's working buffer size.
	// Low values give less lag but may produce audio side effects.
	// Test and use the lowest possible value.
	private int bufferSize = DEBUG ? 1024 : 512;

	// Another buffer size. I don't know what it is but keep it low.
	private int bufferSizeRender = DEBUG ? 4096 : 2048;

	// Working sample rate. Best when it matches file sample rates.
	// oversampling is bugged.
	static float sampleRate = 22050;
	// This is the delay in seconds for playing the spoken option sounds.
	// This allows slightly separating "wheel clicks" from spoken audio.
	static float optiondelay = 0.1f;

	static int PlayingChannels = 5;

	private int lastIndex = -1;

	private int currentIndex = 0;

	private double currentAngle, prevSelectedAngle, lastAngle;

	private HashMap<Character, Integer> letterTable;

	private Character currentLetter = null;
	
	private boolean fast = false;

	// This is the set of UI (clicks) audio files.
	static String[] UIFiles = new String[] { "/ui/clickwheel01",
			"/ui/browseTop", "/ui/browseBottom" };

	static String[] UIFiles2 = new String[] { "/ui/press", "/ui/release",
			"/ui/clickwheel03", "/ui/now", "/ui/browseBottom", // for debug
			"/ui/futurebeep", // for debug
			"/ui/browseBottom" };

	private final float[] SOUND_POSITIONS = { 0.5F, 0.75F, 1.0F, 0.75F, 0.5F,
			0.25F, 0.0F, 0.25F };

	private SoundSet ui, UISounds2;

	// the current coordinates of the point.
	private int x, y;

	private TreeModel current, list, letters, sections;

	private int level = 0;

	private int prevSelection = -1;

	private SimpleDriver sd;

	private int prevPosition = Constants.POSITION_UP;

	public MenuHandler(SimpleDriver sd) {
		this.sd = sd;
		// just need to hold 26 letters
		letterTable = new HashMap<Character, Integer>(26);
		letters = new TreeModel();
		sections = new TreeModel();
	}

	public void setCoordinates(int x, int y) {
		this.x = x;
		this.y = y;
	}

	public int getNumMenuAtCurrentLevel() {
		// face up
		if (sd.getCurrentPosition() == Constants.POSITION_UP) {
			return 12;
		}
		// face south
		else if (sd.getCurrentPosition() == Constants.POSITION_SOUTH) {
			return 8;
		}
		// face west
		else {
			return 10;
		}
		// return 12;
	}

	public void updateMenu() {
		// Vector<Character> t = (Vector<Character>)letterTable.keySet();
		// if the position is changed.
		if (sd.getCurrentPosition() != prevPosition) {
			// if from letter selection to song list
			if (sd.getCurrentPosition() == Constants.POSITION_UP) {
				if (prevPosition == Constants.POSITION_WEST) {
					// find the first character
					Character f = current.getChild(currentIndex).getName()
							.charAt(0);
					// find the correponding index
					current.getSoundSet().stopAll();
					currentIndex = letterTable.get(f);
					current = list;
				} else {
					int i,j;
					i = letterTable.get(currentLetter);
					char c = (char)(currentLetter.charValue() + 1);
					if (letterTable.containsKey(c)){
						j = letterTable.get(c);
					}else {
						j = list.childCount()-1;
					}
					int gap = (j - i + 1)/ 8;
					currentIndex = i + (currentIndex+1)*gap;
					current.getSoundSet().stopAll();
					current = list;
				}
			}
			if (sd.getCurrentPosition() == Constants.POSITION_WEST) {
				// get the first letter
				Character f = current.getChild(currentIndex).getName()
						.charAt(0);
				// map it to letter table
				Vector<Character> tmp = new Vector<Character>();
				tmp.addAll(letterTable.keySet());
				Collections.sort(tmp);
				current.getSoundSet().stopAll();
				currentIndex = tmp.indexOf(f);
				current = letters;
			}
			if (sd.getCurrentPosition() == Constants.POSITION_SOUTH
					&& prevPosition == Constants.POSITION_UP) {
				currentLetter = current.getChild(currentIndex).getName()
				.charAt(0);
				current.getSoundSet().stopAll();
				current = sections;
				currentIndex = 0;
			}
		}
		prevPosition = sd.getCurrentPosition();
		playSound(currentIndex);
	}

	public boolean nextLevel(int n) {
		if (current.getSoundSet() != null) {
			current.getSoundSet().stopAll();
		}
		current = current.getChild(n);
		if (current.hasChildren()) {
			level++;
			ui.play(1);
			return true;
		} else {
			// no more child, go back to parent.
			current = current.getParent();
			return false;
		}

	}

	public boolean prevLevel() {
		if (current.getSoundSet() != null) {
			current.getSoundSet().stopAll();
		}
		try {
			if (!current.getParent().getName().equals(current.getName())) {
				current = current.getParent();
				level--;
				ui.play(2);
				return true;
			}
		} catch (Exception e) {

		}
		return false;
	}

	public int getSeletectedItemIndex() {
		double dis = (Math.pow((x - Constants.TP_CENX), 2) + Math.pow(
				Constants.TP_CENY - y, 2));
		// threshold 45000, so when finger is inside the inner cycle, nothing
		// will be triggered.
		if (dis <= 45000 || dis > 230000) {
			return currentIndex;
		}
		currentAngle = computeAngle();
		if (prevSelectedAngle == 0) {
			prevSelectedAngle = currentAngle;
			lastAngle = currentAngle;
		}
		int flag = 0;
		double cumDiff, diff;
		// special case: currentAngle is in (0,90) and prevAngle is in (270,360)
		if (currentAngle < 90.0 && prevSelectedAngle > 270) {
			flag = 1;
			cumDiff = currentAngle - prevSelectedAngle + 360.0;
		}
		// other cases:
		else {
			cumDiff = currentAngle - prevSelectedAngle;
		}
		// check if it's incrementing or decrementing
		if (cumDiff < 0.0) {
			flag = -1;
		} else {
			flag = 1;
		}
		diff = currentAngle - lastAngle;
		// System.out.println(diff);
		if (Math.abs(diff) > 15
				&& sd.getCurrentPosition() == Constants.POSITION_UP) {
			//flag *= 3;
			fast = true;
		} else {
			fast = false;
		}
		int totalItem = getNumMenuAtCurrentLevel();
		double gap = 360.0 / totalItem;
		// check if the change in angle is big enough to change the index.
		if (Math.abs(cumDiff) >= gap) {
			currentIndex += flag;
			prevSelectedAngle = currentAngle;
			currentIndex = Math.max(currentIndex, 0);
			currentIndex = Math.min(currentIndex, current.childCount() - 1);
		}
		lastAngle = currentAngle;
		return currentIndex;
	}

	public boolean reachedLeafNode() {
		return !current.hasChildren();
	}

	public String getItemText(int index) {
		if (index != -1 && index < current.childCount()) {
			return current.getChild(index).getName();
		}
		return current.getName();
	}

	public void playSound(int index) {
		if (prevSelection == -1 || prevSelection != index) {
			prevSelection = index;
		} else if (prevSelection == index) {
			return;
		}
		SoundSet tmp = current.getSoundSet();
		if (tmp == null) {
			return;
		}
		
		if (fast){
			//ui.play(3);
			tmp.play(index, 4);
		} else {
			ui.play(0);
		}
		tmp.play(index);
	}

	/**
	 * compute the angle between the center and the point.
	 */
	private double computeAngle() {
		double diffX = x - Constants.TP_CENX;
		double diffY = Constants.TP_CENY - y;
		double tan = diffX / diffY;
		double angle = Math.atan(tan);
		double output = 0.0;
		if (diffY >= 0 && diffX >= 0) {
			output = angle * 180 / Math.PI;
		} else if (diffY < 0 && diffX >= 0) {
			output = 180 + angle * 180 / Math.PI;
		} else if (diffY < 0 && diffX < 0) {
			output = 180 + angle * 180 / Math.PI;
		} else {
			output = 360 + angle * 180 / Math.PI;
		}
		double n = (double) current.childCount();
		double d = 180.0 / n;
		if (output + d >= 360.0) {
			return output - 360.0 + d;
		}
		return output + d;
	}

	public int getLevel() {
		return level;
	}

	public float[] getCurrentItemWeight() {
		float[] tmp = new float[current.childCount()];
		for (int i = 0; i < tmp.length; i++) {
			if (i == getSeletectedItemIndex()) {
				tmp[i] = 1.f;
			} else {
				tmp[i] = 1.0f;
			}
		}
		return tmp;
	}

	/**
	 * This is part of the initialization. All the soundsets will be initialized
	 * in formatComponent and subcomponents, and stored in the hashmap
	 * 
	 */
	public void init(String file) {
		SaxSunburstLoader ssl = new SaxSunburstLoader();
		ssl.loadMenus(file);
		list = ssl.getDefaultTreeModel();
		current = list;
		// ==>< First, configure JASS.
		SoundManager.configure(soundAPI, sampleRate, bufferSize,
				bufferSizeRender);
		String[] files = { "clickwheel02.wav", "now.wav", "back.wav","futurebeep.wav"};
		ui = new SoundSet("menu feedback", files.length);
		for (int i = 0; i < files.length; i++) {
			ui.setSample(i, "./ExperimentAudio/andrewaudio/ui/" + files[i], 0,
					0);
		}
		ui.setVolume(0.6f);
		ui.setBlending(5);

		// recursively build sound set for each branch
		buildUISoundSet(current);
		// ==> format the component and establish the soundset for them
		// Launch JASS player.
		SoundManager.init();
	}

	public void reset() {
		current = list;
	}

	/**
	 * precondition: input list is sorted in alphabetical order.
	 * 
	 * @param m
	 */
	private void buildUISoundSet(TreeModel m) {
		int num = m.childCount();
		float location = 0f;
		// if no child, skip constructing menutree.
		if (num == 0) {
			return;
		}
		// int playingChannels = 5; // UI sounds are additive
		SoundSet uisound = new SoundSet(m.getName(), num);
		SoundSet letterSound = new SoundSet("letters", 30);
		SoundSet secs = new SoundSet("sections", 8);
		for (int index = 0; index < 8; index++) {
			TreeModel tmp = new TreeModel();
			tmp.setName("Section" + (index + 1));
			tmp.setIndex(index);
			sections.addChild(tmp);
			location = indexToLocation(index, 8);
			secs.setSample(index, "./ExperimentAudio/andrewaudio/ui/section"
					+ (index + 1) + ".wav", location, optiondelay);
		}
		sections.setSoundSet(secs);
		int count = 0;
		for (int index = 0; index < num; index++) {
			TreeModel tmp = m.getChild(index);
			// get the first letter
			Character f = tmp.getName().charAt(0);
			if (!letterTable.containsKey(f)) {
				letterTable.put(f, index);
				TreeModel l = new TreeModel();
				l.setName(f.toString());
				l.setIndex(index);
				letters.addChild(l);
				location = indexToLocation(count, 26);
				letterSound.setSample(count,
						"./ExperimentAudio/options-countries/" + f + ".wav",
						location, optiondelay);
				count++;
			}
			location = indexToLocation(index, num);
			uisound.setSample(index, "./" + tmp.getSoundFile(), location,
					optiondelay);
			buildUISoundSet(tmp);
		}
		uisound.setVolume(0.6f);
		uisound.setBlending(2, 0.5f, 0f, 0.1f);
		letterSound.setVolume(0.6f);
		letterSound.setBlending(2, 0.5f, 0f, 0f);
		m.setSoundSet(uisound);
		letters.setSoundSet(letterSound);
	}
	
	public void clearAngle(){
		prevSelectedAngle = 0;
	}

	/**
	 * transform any index number within a set to a float point number that can
	 * be used in the system to place the sound. For example index 9 out of 12
	 * total number of directions will corresponding to index 6 in 8 directions
	 * locations are: 0=N, 1=NE, 2=E, 3=SE, 4=S, 5=SW, 6=W, 7=NW
	 * 
	 * @param index
	 * @param totalNumber
	 * @return
	 */
	private float indexToLocation(int index, int totalNumber) {
		return ((float) index / totalNumber) * 8.0F;
	}

}
